/*
* Copyright (c) 2013 Mike Heath. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package loggregator;
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.security.GeneralSecurityException;
import java.security.MessageDigest;
import java.util.Arrays;
import java.util.concurrent.ThreadLocalRandom;
/**
* Used for signing Loggregator messages.
*
* @author Mike Heath <elcapo@gmail.com>
*/
class MessageSigner {
private static final int BLOCK_SIZE = 16;
private final SecretKey secretKey;
public MessageSigner(String secret) {
try {
final MessageDigest digest = MessageDigest.getInstance("SHA-256");
final byte[] hash = digest.digest(secret.getBytes());
final byte[] key = Arrays.copyOf(hash, BLOCK_SIZE);
secretKey = new SecretKeySpec(key, "AES");
} catch (GeneralSecurityException e) {
throw new LoggregatorException(e);
}
}
public byte[] sign(Messages.LogMessage message) {
try {
final MessageDigest digest = MessageDigest.getInstance("SHA-256");
final byte[] hash = digest.digest(message.getMessage().toByteArray());
final byte[] iv = new byte[BLOCK_SIZE];
ThreadLocalRandom.current().nextBytes(iv);
final Cipher cipher = createCipher(Cipher.ENCRYPT_MODE, iv);
byte[] cipherText = cipher.doFinal(pad(hash));
byte[] signature = new byte[iv.length + cipherText.length];
System.arraycopy(iv, 0, signature, 0, iv.length);
System.arraycopy(cipherText, 0, signature, iv.length, cipherText.length);
return signature;
} catch (GeneralSecurityException e) {
throw new LoggregatorException(e);
}
}
/**
* Creates a cipher used for signing logging events
*
* @param mode the cipher mode (encrypt/decrypt)
* @param iv the initialization vector
* @return An AES cipher
* @throws GeneralSecurityException
*/
private Cipher createCipher(int mode, byte[] iv) throws GeneralSecurityException {
final Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding");
cipher.init(mode, secretKey, new IvParameterSpec(iv));
return cipher;
}
/**
* Apparently Go doesn't have built-in PKCS#7 padding so we're using this home baked padding.
*
* @param text the plain text to be padded.
* @return the padded plain text
*/
private byte[] pad(byte[] text) {
final int bytesToPad = BLOCK_SIZE - text.length % BLOCK_SIZE;
final byte[] paddedText = Arrays.copyOf(text, text.length + bytesToPad);
paddedText[text.length] = (byte)0x80;
for (int i = text.length + 1; i < paddedText.length; i++) {
paddedText[i] = 0;
}
return paddedText;
}
/**
* Remove the padding from the plain text.
*
* @param text the padded plain text
* @return the plain text without the padding
*/
private byte[] unpad(byte[] text) {
int unpaddedLength = text.length - 1;
while (text[unpaddedLength] == 0) {
unpaddedLength--;
}
if (text[unpaddedLength] != 0x80) {
throw new LoggregatorException("Bad padding");
}
return Arrays.copyOf(text, unpaddedLength);
}
}